// OpenZapper
// http://openzapper.codeplex.com/
// Appache License Version 2.0 


using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;
using System.Collections;
using MicroLiquidCrystal;
using SecretLabs.NETMF.Hardware.NetduinoPlus;

namespace OpenZapper
{
    public interface IZapperCallback
    {
        void ZapperStandby();
        void ZapperRunning();
        void ZapperPauseEnded();
    }

    public class ZapperCycleThread: IDisposable
    {
        PWM _zapperPwm;
        OutputPort _softPWM;
        ConfigReader _config;
        bool _running = false;
        private Thread m_Thread = null;
        ZapperProfiles  _currentProfile;
        IZapperCallback _callback;
        Lcd _lcd;

        public ZapperCycleThread(PWM zapperPwm, ConfigReader config,Lcd lcd, IZapperCallback callback)
        {
            _zapperPwm  = zapperPwm;
            _config     = config;
            _callback   = callback;
            _lcd        = lcd;
        }

        public void Start(ZapperProfiles Profile)
        {
            _currentProfile = Profile;
            if (null == m_Thread)
            {
                m_Thread            = new Thread(startThread);
                m_Thread.Priority   = ThreadPriority.Normal;
                m_Thread.Start();
            }
        }

        public void Stop()
        {
            _running = false;
            StopZapper();
            //_zapperPwm.Stop();
            _callback.ZapperStandby();
        }

        private void startThread()
        {
            _running = true;
            StopZapper();
            _zapperPwm.Start();
            _callback.ZapperStandby() ;
            //getting Sequence Mode:
            ArrayList profileData = _config.getProfileData(_currentProfile);
            if (profileData.Count > 0)
            {
                int MaxElementsIdx = profileData.Count - 1;
                if (_currentProfile.Random == true)//random mode
                {
                    for (int i = 0; i < _config.CyclesToDo; i++)
                    {
                        long defaultCycleDuration = _config.ActiveDuration;
                        Random rnd = new Random((int)DateTime.Now.Ticks);
                        while (defaultCycleDuration > 0 && _running)
                        {
                            int itemIdx = rnd.Next(MaxElementsIdx);
                            defaultCycleDuration = PerformFrequencyStep((ZapperFrequencies)(profileData[itemIdx]), defaultCycleDuration);
                        }
                        PerformPause(_config.PauseDuration);
                        if (_running == false)
                        {
                            break;
                        }
                    }
                }
                else//sequential mode
                {
                    int currentIdx = 0;
                    for(int i = 0; i < _config.CyclesToDo; i++)
                    {
                        long defaultCycleDuration = _config.ActiveDuration;
                        while (defaultCycleDuration > 0 && _running)
                        {
                            defaultCycleDuration = PerformFrequencyStep((ZapperFrequencies)(profileData[currentIdx]), defaultCycleDuration);
                            currentIdx++;
                            if (currentIdx >= (profileData.Count))
                            {
                                currentIdx = 0;
                            }
                        }
                        if (_running == false)
                        {
                            break;
                        }
                        PerformPause(_config.PauseDuration);
                    }
                }
            }
        }

        private void StartZapper()
        {
            SetZapperFrequency(2500, 0.5);
        }
        private void StopZapper()
        {
            if (_zapperPwm != null)
            {
                SetZapperFrequency(2500, 0);
            }
        }
        private void PerformPause(long pauseDuration)
        {
            long Duration = pauseDuration;
            StopZapper();
            //_zapperPwm.Stop();
            _callback.ZapperStandby();
            for (int i = 0; i < pauseDuration; i++)
            {
                Thread.Sleep(1000);
                Duration -= 1;
                _lcd.Clear();
                _lcd.WriteLine(0, "Pause Cycle");
                _lcd.WriteLine(1, "Ramaining " + Duration + "s");
                if (Duration <= 0)
                {
                    break;
                }
                if (_running == false)
                {
                    return;
                }
            }
            _callback.ZapperPauseEnded();
        }

        private long PerformFrequencyStep(ZapperFrequencies data, long defaultCycleDuration)
        {
            //_zapperPwm.Stop();
            StopZapper();
            long Frequency = data.Frequency;
            if (Frequency > 16)
            {

                if (SetZapperFrequency(Frequency, data.Ratio / 100) == false)
                {
                    _lcd.Clear();
                    _lcd.WriteLine(0, "Running Min 16Hz");
                    Frequency = 16;
                    SetZapperFrequency(Frequency, data.Ratio / 100);
                }
                else
                {
                    _lcd.Clear();
                    _lcd.WriteLine(0, "Running " + Frequency + "Hz");
                }

                //double dFrequencyKhz = ((double)(data.Frequency) / 1000.0);

                //_zapperPwm.Start();
                long CurrentFrequencyDuration = data.DurationSec;
                for (int i = 0; i < data.DurationSec; i++)
                {
                    Thread.Sleep(1000);
                    CurrentFrequencyDuration -= 1;
                    defaultCycleDuration -= 1;
                    _lcd.Clear();
                    _lcd.WriteLine(0, "Running " + Frequency + "Hz");
                    _lcd.WriteLine(1, CurrentFrequencyDuration + "s|Cycle:" + defaultCycleDuration + "s");
                    if (defaultCycleDuration <= 0)
                    {
                        break;
                    }
                    if (_running == false)
                    {
                        return defaultCycleDuration;
                    }
                }
            }
            else
            {
                StartSlowFrequencies(Frequency, data.Ratio / 100, data.DurationSec);
                defaultCycleDuration -= data.DurationSec;
            }
            return defaultCycleDuration;
        }

        private void StartSlowFrequencies(long Frequency, double Ratio, double Duration)
        {
            //_zapperPwm.Stop();
            //_zapperPwm.Dispose();
            //_zapperPwm = null;
            //_softPWM = new OutputPort(Pins.GPIO_PIN_D6, true);

            double DurationMs = Duration * 1000;
            double periodDuration       = (double)(1000 / Frequency);
            int ActiveStateDuration     = (int)(periodDuration * Ratio);
            int InactiveStateDuration   = (int)(periodDuration - ActiveStateDuration);
            _lcd.Clear();
            _lcd.WriteLine(0, "Running " + Frequency + "Hz");
            _lcd.WriteLine(1, "For " + Duration + "s ...");
            _zapperPwm.Frequency = 2500;
            while (DurationMs > 0 && _running == true)
            {
                /*_softPWM.Write(false);//invert values to cope with transistor invertion
                Thread.Sleep(ActiveStateDuration);
                _softPWM.Write(true);//invert values to cope with transistor invertion
                Thread.Sleep(InactiveStateDuration);
                DurationMs -= periodDuration;*/
                _zapperPwm.DutyCycle = 0;//ration inverted
                Thread.Sleep(ActiveStateDuration);
                _zapperPwm.DutyCycle = 1;//ration inverted
                Thread.Sleep(InactiveStateDuration);
                DurationMs -= periodDuration;

            }

            /*_softPWM.Dispose();
            _softPWM    = null;
            _zapperPwm  = new PWM(Cpu.PWMChannel.PWM_1, 2500, 1, false);*/
        }

        public void Dispose()
        {
            if (_running == true)
            {
                Stop();
                if (null != m_Thread)
                {
                    try
                    {
                        m_Thread.Suspend();
                        m_Thread = null;
                    }
                    catch (Exception)
                    {
                        Debug.GC(false);
                    }
                    finally { }
                }
            }
        }
        private bool SetZapperFrequency(double frequency, double ratio)
        {
            bool bStatus = false;
            if (null == _zapperPwm)
            {
                return false;
            }
            try
            {
                _zapperPwm.DutyCycle = 1 - ratio;
                _zapperPwm.Frequency = frequency;
                bStatus = true;
                _callback.ZapperRunning();
            }
            catch (Exception ex)
            {
                _zapperPwm.Frequency = 16;
                Debug.Print(ex.Message);
            }
            return bStatus;
        }
    }
}
